/**
 * @namespace CSS stuff
 */
Jelo.CSS = function() {
    /**
     * @private Resets the opacity style of a DOM element. Required for IE.
     */
    var clearOpacity = function(el) {
        if (Jelo.Env.isIE) {
            if (typeof el.style.filter === "string" && (/alpha/i).test(el.style.filter)) {
                el.style.filter = "";
            }
        } else {
            el.style.opacity = "";
            el.style["-moz-opacity"] = "";
            el.style["-khtml-opacity"] = "";
        }
    };
    
    /** @private alias */
    var toCamel = Jelo.Format.toCamel;
    
    /** @scope Jelo.CSS */
    return {
        /**
         * Mainly used by internal functions, clearOpacity fixes IE behavior.
         * 
         * @function
         * @param {HTMLElement} element
         */
        clearOpacity : clearOpacity,
        
        /**
         * @function
         * @param {HTMLElement} element The item to which the class should be
         *        assigned.
         * @param {String} class The class name to assign. Duplicates will be
         *        filtered out automatically.
         */
        addClass     : function(e, c) {
            if (Jelo.Valid.isArray(e)) {
                var fn = arguments.callee;
                Jelo.each(e, function() {
                    fn(this, c);
                });
                return;
            }
            var curr = e.className;
            if (!Jelo.CSS.hasClass(e, c)) {
                e.className = curr + (curr.length ? " " : "") + c;
            }
        },
        
        /**
         * @function
         * @param {HTMLElement} element The item to investigate.
         * @param {String} class The class name to search for.
         * @returns {Boolean} True if the item's className property contains the
         *          supplied class name.
         */
        hasClass     : function(e, c) {
            return c && (' ' + e.className + ' ').indexOf(' ' + c + ' ') != -1;
        },
        
        /**
         * @function
         * @param {HTMLElement} element The item to affect.
         * @param {String} class The class name to remove.
         */
        removeClass  : function(e, c) {
            if (Jelo.Valid.isArray(e)) {
                var fn = arguments.callee;
                Jelo.each(e, function() {
                    fn(this, c);
                });
                return;
            }
            e.className = e.className.replace(new RegExp('\s?' + c + '\s?', 'i'), '').replace(/^\s?|\s?$/g, '');
        },
        
        /**
         * Gets the current or computed value for an element's CSS property.
         * 
         * @function
         * @param {HTMLElement|HTMLElement[]|String} element One or more items
         *        to investigate. If a string is supplied, it is considered a
         *        CSS selector and will match elements accordingly.
         * @param {String} property The CSS property to retrieve
         * @return {String} CSS property value
         */
        getStyle     : function() {
            var view = document.defaultView;
            return (view && view.getComputedStyle) ? function(el, p, toInt) {
                var /* counter */i, /* value */v, /* return value */ret, /* camelCase property */cp, styles = [];
                if (!el || !p) {
                    return null;
                }
                if (typeof el == "string") {
                    el = Jelo.Dom.select(el);
                }
                if (typeof p == "string") {
                    p = p.toLowerCase();
                }
                if (Jelo.Valid.isArray(el)) {
                    for (i = 0; i < el.length; i++) {
                        styles.push(Jelo.CSS.getStyle(el[i], p, toInt));
                    }
                    return styles;
                }
                if (Jelo.Valid.isArray(p)) {
                    for (i = 0; i < p.length; i++) {
                        styles.push(Jelo.CSS.getStyle(el, p[i], toInt));
                    }
                    return styles;
                }
                if (p == "float") {
                    p = "cssFloat";
                }
                cp = toCamel(p);
                switch (cp) {
                    case "backgroundPositionX" :
                        try {
                            ret = Jelo.CSS.getStyle(el, "background-position").split(" ")[0];
                        } catch (e1) {
                            return null;
                        }
                        break;
                    case "backgroundPositionY" :
                        try {
                            ret = Jelo.CSS.getStyle(el, "background-position").split(" ")[1];
                        } catch (e2) {
                            return null;
                        }
                        break;
                    default :
                        if ((v = el.style[p])) {
                            ret = (/color/).test(p)
                                ? Jelo.Format.rgbToHex(v).toLowerCase()
                                : v.toString().toLowerCase();
                        } else if ((v = view.getComputedStyle(el, "")[cp])) {
                            ret = (/color/).test(p) ? Jelo.Format.rgbToHex(v) : v.toString();
                        }
                }
                return toInt ? parseInt(ret, 10) : ret;
            } : function(el, p, toInt) {
                var /* counter */i, /* value */v, /* return value */ret, /* camelCase property */cp, styles = [];
                if (!el || !p) {
                    return null;
                }
                if (typeof el == "string") {
                    el = Jelo.Dom.select(el);
                }
                if (typeof p == "string") {
                    p = p.toLowerCase();
                }
                if (Jelo.Valid.isArray(el)) {
                    for (i = 0; i < el.length; i++) {
                        styles.push(Jelo.CSS.getStyle(el[i], p, toInt));
                    }
                    return styles;
                }
                if (Jelo.Valid.isArray(p)) {
                    for (i = 0; i < p.length; i++) {
                        styles.push(Jelo.CSS.getStyle(el, p[i], toInt));
                    }
                    return styles;
                }
                if (p == "opacity") {
                    if (typeof el.style.filter == 'string') {
                        var m = el.style.filter.match(/alpha\(opacity=(.*)\)/i);
                        if (m) {
                            var fv = parseFloat(m[1]);
                            if (!isNaN(fv)) {
                                return fv ? fv / 100 : 0;
                            }
                        }
                    }
                    return 1;
                }
                if (p == "float") {
                    p = "styleFloat";
                }
                p = toCamel(p);
                if ((v = el.style[p])) {
                    ret = v.toString();
                } else if (el.currentStyle && (v = el.currentStyle[p])) {
                    if (v == "auto") {
                        if ((v = el["offset" + p.replace(/^(.)/, function(m, l) {
                            return l.toUpperCase(); // initial cap
                        })])) {
                            ret = v + "px";
                        }
                    }
                    ret = v.toString();
                }
                return toInt ? parseInt(ret, 10) : ret;
            };
        }(),
        
        /**
         * Gets the current or computed value for an element's CSS property.
         * 
         * @function
         * @param {HTMLElement|HTMLElement[]|String} element One or more items
         *        to affect. If a string is supplied, it is considered a CSS
         *        selector and will match elements accordingly.
         * @param {String|String[]} property The CSS property to assign.
         * @param {String|String[]} value The value to assign.
         */
        setStyle     : function(el, p, v) {
            var /* counter */i, /* units */u;
            if (typeof el === "string") {
                el = Jelo.Dom.select(el);
            }
            if (Jelo.Valid.isArray(el)) {
                for (i = 0; i < el.length; i++) {
                    Jelo.CSS.setStyle(el[i], p, v);
                }
                return;
            }
            if (Jelo.Valid.isArray(p) || Jelo.Valid.isArray(v)) {
                if (Jelo.Valid.isArray(p) && Jelo.Valid.isArray(v) && (p.length == v.length)) {
                    for (i = 0; i < p.length; i++) {
                        Jelo.CSS.setStyle(el, p[i], v[i]);
                    }
                } else {
                    throw new Error('Jelo.CSS.setStyle: Properties and values must both be Arrays with the same length, or both be Strings.');
                }
                return;
            }
            p = toCamel(p);
            if ((/width|height|top|right|bottom|left|size/).test(p)) {
                u = v.replace(/[^(%|px|em)]/g, "");
                if (!u.length) {
                    u = "px";
                }
                v = parseInt(v, 10);
                if (isNaN(v)) {
                    v = 0;
                }
                v += u;
            }
            var s = el.style;
            if (p === "opacity") {
                if (Jelo.Env.isIE) {
                    s.zoom = 1;
                    s.filter = (s.filter || '').replace(/alpha\([^\)]*\)/gi, "") +
                        (v == 1 ? "" : " alpha(opacity=" + v * 100 + ")");
                } else {
                    s.opacity = parseFloat(v);
                }
            } else {
                s[p] = v;
            }
        },
        
        /**
         * Generates a random hexidecimal color, including the hash symbol (e.g.
         * #5181ff)
         * 
         * @returns {String} A "CSS-formatted" color.
         */
        randomColor  : function() {
            return '#' + (function(h) {
                return new Array(7 - h.length).join('0') + h;
            })((Math.random() * (0xFFFFFF + 1) << 0).toString(16));
        },
        
        /**
         * Gets a CSS stylesheet rule.
         * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
         * 
         * @function
         * @param {String} selector CSS selector, exactly as entered in the stylesheet.
         * @param {Boolean=false} deleteFlag True to delete the matching rule.
         */
        getRule      : function(s, d) {
            s = s.toLowerCase && s.toLowerCase();
            if (document.styleSheets) {
                for (var i = 0; i < document.styleSheets.length; i++) {
                    var styleSheet = document.styleSheets[i];
                    var ii = 0; // TODO: convert to for loop
                    var cssRule = false;
                    do {
                        if (styleSheet.cssRules) {
                            cssRule = styleSheet.cssRules[ii];
                        } else {
                            cssRule = styleSheet.rules[ii];
                        }
                        if (cssRule) {
                            if (cssRule.selectorText.toLowerCase() == s) {
                                if (d) {
                                    if (styleSheet.cssRules) {
                                        styleSheet.deleteRule(ii);
                                    } else {
                                        styleSheet.removeRule(ii);
                                    }
                                    return true;
                                } else {
                                    return cssRule;
                                }
                            }
                        }
                        ii++;
                    } while (cssRule)
                }
            }
            return false;
        },
        /**
         * Deletes a CSS stylesheet rule.
         * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
         * 
         * @function
         * @param {String} selector CSS selector, exactly as entered in the stylesheet.
         */
        deleteRule   : function(s) {
            return Jelo.CSS.getRule(s, true);
        },
        /**
         * Creates a new CSS stylesheet rule.
         * http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
         * 
         * @function
         * @param {String} selector CSS selector, exactly as entered in the stylesheet.
         * @returns {CSSRule} A new rule that can be modified as follows:
         * var r = Jelo.CSS.createRule('#test'); r.style.color = 'green';
         */
        createRule   : function(s) {
            if (document.styleSheets) {
                if (!getRule(s)) {
                    if (document.styleSheets[0].addRule) {
                        document.styleSheets[0].addRule(s, null, 0);
                    } else {
                        document.styleSheets[0].insertRule(s + ' { }', 0);
                    }
                }
            }
            return Jelo.CSS.getRule(s);
        }
    };
}();

/**
 * Convenience method. If two arguments are supplied, this is shorthand for
 * {@link Jelo.CSS.getStyle}. If three arguments are supplied, this is
 * shorthand for {@link Jelo.CSS.setStyle}.
 * 
 * @function
 * @name css
 * @memberOf Jelo
 */
Jelo.css = function() {
    if (arguments.length == 2) {
        return Jelo.CSS.getStyle(arguments[0], arguments[1]);
    }
    if (arguments.length == 3) {
        return Jelo.CSS.setStyle(arguments[0], arguments[1], arguments[2]);
    }
    throw new Error("Jelo.css(element, property) for getStyle, and Jelo.css(element, property, value) for setStyle.");
};
